package com.bby.disk.manager.virtual;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.bby.common.exception.BBYException;
import com.bby.common.model.UserBusinessInfo;
import com.bby.common.model.Permission;
import com.bby.common.model.UserGroup;
import com.bby.common.model.UserSettings;
import com.bby.common.utils.StatusCode;
import com.bby.disk.bean.vo.VirtualFileList;
import com.bby.disk.manager.auth.AuthorityManager;
import com.bby.disk.service.IFileService;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.model.Projections;
import com.mongodb.client.result.UpdateResult;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.json.JsonWriterSettings;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

import static com.mongodb.client.model.Filters.*;
import static com.mongodb.client.model.Projections.include;
import static org.springframework.data.mongodb.core.query.Criteria.where;

/**
 * 虚拟文件管理
 */
public class VirtualManager {
    public static MongoTemplate mongoTemplate;
    /**
     * 设置集合名称
     */
    public static final String FILES_COLLECTION_NAME = "files";
    public static final String USER_COLLECTION_NAME = "userBusinessInfo";


    /* ************** 修改 ***************** */

    /**
     * 新建用户,根目录id与userId相同
     */
    public static String initUser(String userId) {
        VirtualDirectory root = new VirtualDirectory(userId, "admin");
        root.setId(userId).setOwner(userId);
        root = mongoTemplate.insert(root, FILES_COLLECTION_NAME);
        return root.getId();
    }

    /**
     * 重命名
     */
    public static void rename(String fileId, String newName) throws VirtualObjNotFoundException {
        edit(fileId, "name", newName);
    }

    /**
     * 移动
     *
     * @param fileId      文件id
     * @param targetDirId 目的文件夹
     */
    public static void move(String fileId, String targetDirId) throws BBYException {
        VirtualObject file = findFile(fileId);
        checkId(targetDirId);
        edit(fileId, "parent", targetDirId);
        mongoTemplate.updateFirst(
                new Query(criteriaIdIs(file.parent))
                , new Update().pull("files", fileId)
                , FILES_COLLECTION_NAME);
        mongoTemplate.updateFirst(
                new Query(criteriaIdIs(targetDirId))
                , new Update().addToSet("files", fileId)
                , FILES_COLLECTION_NAME);

    }

    /**
     * 复制
     */
    public static void copy(String owner,String fileId, String targetDirId, Permission permission) throws BBYException {
        VirtualObject file = findFile(fileId);
        file.setId(null);
        file.parent = targetDirId;
        file.setOwner(owner);
        file.setPermission(permission);
        if (file.isFile()) {
            create(file);
            return;
        }
        VirtualDirectory dir = ((VirtualDirectory) file);
        List<String> files = dir.getFiles();
        // 清空
        dir.setFiles(new ArrayList<>());
        String parent = create(file).getId();
        if (ObjectUtil.isNotEmpty(files)) {
            for (String child : files) {
                copy(owner,child, parent, permission);
            }
        }
    }
    /**
     * 修改权限
     */
    public static void editPermission(VirtualObject file, Permission newPermission) throws VirtualObjNotFoundException {
        if (!file.isFile()) {
            List<VirtualObject> files = findFiles(((VirtualDirectory) file).getFiles());
            for (VirtualObject f : files) {
                editPermission(f, newPermission);
            }
        }
        edit(file.id, "permission", newPermission);
    }

    public static void edit(String fileId, String key, Object value) throws VirtualObjNotFoundException {
        checkId(fileId);
        mongoTemplate.updateFirst(
                new Query(criteriaIdIs(fileId))
                , new Update().set(key, value)
                , FILES_COLLECTION_NAME);
    }

    /**
     * 删除文件，自动寻找上级目录，删除信息
     *
     * @param fileId 要删除的文件id
     * @throws VirtualObjNotFoundException
     */
    public static void delete(String fileId) throws BBYException {
        VirtualObject file = findFile(fileId);
        if (!file.isFile()) {
            try {// 删除子文件
                for (String id : ((VirtualDirectory) file).files) {
                    delete(id);
                }
            } catch (Exception ignored) {
            }
        }else {
            storageUseUpload(file.getOwner(),-((VirtualFile)file).size);
        }
        // 删除自己
        mongoTemplate.updateFirst(
                new Query(criteriaIdIs(file.parent))
                , new Update().pull("files", fileId)
                , FILES_COLLECTION_NAME);
        mongoTemplate.remove(new Query(criteriaIdIs(fileId)), FILES_COLLECTION_NAME).getDeletedCount();
    }


    /**
     * 创建目录或者文件
     *
     * @return
     */
    //    @Transactional(rollbackFor = RuntimeVirtualObjNotFindException.class) 单机不支持
    public static VirtualObject create(VirtualObject file) throws BBYException {
        checkId(file.getParent());
        if (!file.isFile()) {
            boolean exists = mongoTemplate.exists(new Query(
                    where("parent").is(file.getParent())
                            .and("name").is(file.getName())), FILES_COLLECTION_NAME);
            if (exists) {
                throw new BBYException(StatusCode.FILE_ALREADY_EXIST, "文件夹已经存在！");
            }
        }else {
            storageUseUpload(file.getOwner(),((VirtualFile)file).size);
        }
        file = mongoTemplate.insert(file, FILES_COLLECTION_NAME);
        if (ObjectUtil.isNotEmpty(file.id)) {
            Update update = new Update().addToSet("files", file.id);
            Query query = new Query(criteriaIdIs(file.getParent()));
            mongoTemplate.updateFirst(query, update, FILES_COLLECTION_NAME);
        }
        return file;
    }

    /**************** 查询 ******************/

    /**
     * 搜索某个人的文件
     */
    public static MongoCursor<Document> searchByOwner(String owner, String keyword, String type) {
        Bson query = and(
                eq("owner", owner),
                regex("name", keyword, "i"));
        if ("file".equals(type)) {
            query = and(eq("file", true), query);
        } else if ("dir".equals(type)) {
            query = and(eq("file", false), query);
        }
        return mongoTemplate.getCollection(FILES_COLLECTION_NAME)
                .find(query).cursor();
    }

    /**
     * 全局搜索
     */
    public static MongoCursor<Document> searchGlobal(String keyword, String type) {
        Bson query = regex("name", keyword, "i");
        if ("file".equals(type)) {
            query = and(eq("file", true), query);
        } else if ("dir".equals(type)) {
            query = and(eq("file", false), query);
        }
        return mongoTemplate.getCollection(FILES_COLLECTION_NAME)
                .find(query).cursor();
    }

    /**
     * 获取目录下的文件列表
     */
    public static VirtualFileList loadList(VirtualDirectory dir) {
        List<String> files = dir.files;
        VirtualFileList list = new VirtualFileList(dir);
        if (files.size() == 0) {
            return list;
        }
        list.setList(findFiles(files));
        return list;
    }

    /**
     * 获取目录下的文件列表
     */
    public static VirtualFileList loadList(String dirId) throws VirtualObjNotFoundException {
        return loadList(findDirectory(dirId));
    }

    /**
     * 获取目录下的文件列表,以路径方式
     */
    public static VirtualFileList loadList(VirtualPath path) throws VirtualObjNotFoundException {
        VirtualObject dir = findFileByPath(path);
        if (dir.isFile()) {
            throw new VirtualObjNotFoundException(path.getPath());
        }
        return loadList((VirtualDirectory) dir);
    }

    public static VirtualDirectory findDirectory(String dirId) throws VirtualObjNotFoundException {
        VirtualObject dir = findFile(dirId);
        if (dir.isFile()) {
            throw new VirtualObjNotFoundException(dirId);
        }
        return (VirtualDirectory) dir;
    }

    public static VirtualObject findFile(String id) throws VirtualObjNotFoundException {
        VirtualObject file = mongoTemplate.findById(id, VirtualObject.class, FILES_COLLECTION_NAME);
        if (file == null) {
            throw new VirtualObjNotFoundException(id);
        }
        return file;
    }

    public static List<VirtualObject> findFiles(List<String> ids) {
        if (ObjectUtil.isEmpty(ids)) {
            return new ArrayList<>();
        }
        Criteria[] crs = new Criteria[ids.size()];
        for (int i = 0; i < ids.size(); i++) {
            crs[i] = criteriaIdIs(ids.get(i));
        }
        return mongoTemplate.find(new Query(new Criteria().orOperator(crs)), VirtualObject.class, FILES_COLLECTION_NAME);
    }

    public static VirtualObject findFileByPath(VirtualPath path) throws VirtualObjNotFoundException {
        String[] sps = path.getPath().split(VirtualPath.separator);
        if (ObjectUtil.isNotEmpty(sps)) {
            VirtualObject currentFile = findDirectory(sps[0]);
            for (int i = 1; i < sps.length; i++) {
                currentFile = searchVirtualObjectByName(sps[i], currentFile);
            }
            return currentFile;
        }
        throw new VirtualObjNotFoundException();
    }

    public static String findVirtualPath(String id) throws VirtualObjNotFoundException {
        VirtualObject currentFile = findFile(id);
        if (currentFile == null) {
            throw new VirtualObjNotFoundException();
        }
        if (currentFile.getParent() == null) {
            return currentFile.getName();
        }
        return findVirtualPath(currentFile.getParent()) + VirtualPath.separator + currentFile.getName();
    }

    public static boolean exists(String id) {
        return mongoTemplate.exists(new Query(criteriaIdIs(id)), FILES_COLLECTION_NAME);
    }

    private static VirtualObject searchVirtualObjectByName(String name, VirtualObject file) throws VirtualObjNotFoundException {
        if (file.isFile()) {
            throw new VirtualObjNotFoundException();
        }
        List<String> files = ((VirtualDirectory) file).files;
        if (ObjectUtil.isEmpty(files)) {
            throw new VirtualObjNotFoundException();
        }
        Criteria nameCriteria = where("name").is(name);
        Criteria[] crs = new Criteria[files.size()];
        for (int i = 0; i < files.size(); i++) {
            crs[i] = criteriaIdIs(files.get(i)).andOperator(nameCriteria);
        }
        VirtualObject res = mongoTemplate.findOne(Query.query(new Criteria().orOperator(crs)), VirtualObject.class, FILES_COLLECTION_NAME);
        if (res == null) {
            throw new VirtualObjNotFoundException();
        }
        return res;
    }

    private static Criteria criteriaIdIs(String id) {
        return where("_id").is(new ObjectId(id));
    }

    private static void checkId(String id) throws VirtualObjNotFoundException {
        if (!exists(id)) {
            throw new VirtualObjNotFoundException(id);
        }
    }

    public static List<UserGroup> getUserGroup(String userId) {
        Bson query = eq("_id", new ObjectId(userId));
        Document document = mongoTemplate.getCollection("userBusinessInfo")
                .find(query)
                .projection(include("userGroups"))
                .first();
        assert document != null;
        UserBusinessInfo businessInfo = JSON.parseObject(
                document.toJson(JsonWriterSettings.builder().build()),
                UserBusinessInfo.class);
        return businessInfo.getUserGroups();
    }

    public static MongoCursor<Document> getRootsIterator() {
        Bson query = eq("parent", null);
        return mongoTemplate.getCollection(FILES_COLLECTION_NAME)
                .find(query).projection(include("permission", "owner", "_id")).cursor();
    }

    public static boolean isBlockSearch(String userId) {
        FindIterable<Document> iterable = mongoTemplate.getCollection("userSettings")
                .find(eq("_id", new ObjectId(userId)))
                .projection(Projections.include("blockSearch"));
        Document document = iterable.first();
        assert document != null;
        return document.getBoolean("blockSearch", true);
    }

    /**
     * 文件分类
     *
     * @param userId   用户id
     * @param category image|text|video|audio|zip
     * @return
     */
    public static List<VirtualFile> classify(String userId, String category) {
        String regex = FileType.categorySuffixMap.get(category);
        Query query = new Query(
                where("owner").is(userId)
                        .and("file").is(true)
                        .and("name").regex("\\w*\\.(" + regex + ")$", "i")
        );
        return mongoTemplate.find(query, VirtualFile.class, FILES_COLLECTION_NAME);
    }
    public static UserSettings getUserSettings(String userId) {
        return mongoTemplate.findById(userId,UserSettings.class);
    }
    public static Permission getDefaultPermission(String userId,VirtualObject parent) throws VirtualObjNotFoundException {
        UserSettings userSettings = getUserSettings(userId);
        if ("custom".equals(userSettings.getDefaultPmType())){
            return userSettings.getDefaultPermission();
        }else  if ("extends".equals(userSettings.getDefaultPmType())){
            return parent.getPermission();
        }else return null;
    }

    /**
     * 刷新剩余空间
     */
    public static long storageUseFlush(String userId) {
        FindIterable<Document> iterable = mongoTemplate.getCollection(FILES_COLLECTION_NAME)
                .find(
                        and(
                                eq("owner", userId),
                                eq("file", true)))
                .projection(Projections.include("size"));
        long use=0;

        for (Document document : iterable) {
            use+=document.getLong("size");
        }
        Update update = new Update().set("storageUse",use);
        mongoTemplate.updateFirst(
                new Query(where("_id").is(userId)),
                update,USER_COLLECTION_NAME);
        return use;
    }

    /**
     * 检查剩余空间
     * @param userId 用户id
     * @param size 需要的空间大小
     * @throws BBYException
     */
    public static void storageUseCheck(String userId,long size) throws BBYException {
        Document document = getStorageDocument(userId);
        long total = document.getLong("storageByte");
        long use = document.getLong("storageUse");
        if (use+size>total){
            throw new BBYException(StatusCode.STORAGE_NOT_FREE,"存储空间不足！");
        }
    }
    /**
     *
     * @param userId 用户id
     * @param offset 变化量
     */
    private static void storageUseUpload(String userId,long offset) throws BBYException {
        Document document = getStorageDocument(userId);
        long total = document.getLong("storageByte");
        long use = document.getLong("storageUse");
        if (use+offset>total){
            throw new BBYException(StatusCode.STORAGE_NOT_FREE,"存储空间不足！");
        }
        Update update = new Update().set("storageUse",use+offset);
        UpdateResult result = mongoTemplate.updateFirst(
                new Query(where("_id").is(userId)),
                update, USER_COLLECTION_NAME);
        assert result.getModifiedCount()>0;
    }
    private static Document getStorageDocument(String userId){
        FindIterable<Document> iterable = mongoTemplate.getCollection(USER_COLLECTION_NAME)
                .find(eq("_id", new ObjectId(userId)))
                .projection(Projections.include("storageByte","storageUse"));
        Document document = iterable.first();
        assert document != null;
        return document;
    }
}
